#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import os
import sys

from pycparser.plyparser import template

try:
    from .nodes import *
    from .parser_common import *
    from .func_lexer import GVCFuncLexer
except:
    from nodes import *
    from parser_common import *
    from func_lexer import GVCFuncLexer

import logging
from ctools.logext import *

_fpath = os.path.dirname(os.path.realpath(__file__))
_rlog, _plog, _mlog, _tlog = get_loggers(__file__, __name__, propagate=False)

@template
class GVCFuncParser(GVCParserBase):

    def __init__(self, lexer=GVCFuncLexer) -> None:
        super().__init__(lexer=lexer,
                        yacctab='ctools.ly_parser.func_yacctab',
                        debugfile='func_yacc.out')
        self.root = FuncNode('root')

    def start_parse(self, funcname, lineno=1):
        self.root = FuncNode(funcname)
        self.clex.reset_lineno(lineno)

    # Wrapper around a translation unit, to allow for empty input.
    # Not strictly part of the C99 Grammar, but useful in practice.
    def p_translation_unit_or_empty(self, p):
        """ translation_unit_or_empty   : translation_unit
                                        | empty
        """

        def __add_node(root, nds):
            for nd in nds:
                if isinstance(nd, Node):
                    root.add_child(nd)
                elif isinstance(nd, (list, tuple)):
                    __add_node(root, nd)

        if isinstance(p[1], (list, tuple)):
            # Note: translation_unit is already a list
            __add_node(self.root, p[1])
        p[0] = self.root

    def p_translation_unit_1(self, p):
        """ translation_unit    : external_declaration
        """
        # Note: external_declaration is already a list
        p[0] = p[1]

    def p_translation_unit_2(self, p):
        """ translation_unit    : translation_unit external_declaration
        """
        p[1].extend(p[2])
        p[0] = p[1]

    def p_external_declaration_2(self, p):
        """ external_declaration : binary_expression_decl
                                | callee_expression_decl
                                | var_decl
                                | keep_decl
                                | ctree_decl
        """
        # p[1] is list
        p[0] = p[1]

    def p_keep_decl(self, p):
        """ keep_decl : keep_decl_body SEMI
        """
        p[0] = p[1]

    def p_keep_decl_body(self, p):
        """ keep_decl_body : KEEP KEEPBLOCK
        """
        name = "{}".format(self._token_coord(p, 1))
        kn = KeepNode(name, p[2], self._token_coord(p, 1))
        p[0] = [kn]

    def p_ctree_decl(self, p):
        """ ctree_decl : ctree_decl_body SEMI
        """
        p[0] = p[1]
        _mlog.debug(f'{self._coord(p.lineno(1), 1)} {p[1]}')

    def p_ctree_decl_body(self, p):
        """ ctree_decl_body : CTREE CTREEBLOCK
        """
        name = "{}".format(self._token_coord(p, 1))
        ctn = CTreeNode(name, p[2], self._token_coord(p, 1))
        p[0] = [ctn]
        _mlog.debug(f'{self._coord(p.lineno(1), 1)} {p[1]}')

    def p_var_decl(self, p):
        """ var_decl : var_decl_expr SEMI
                    | ID SEMI
        """
        # ID SEMI take palce binary_expression_decl
        if type(p[1]) is str:
            vn = VarNode('func_t', p[1], self._token_coord(p, 1))
            p[0] = [vn]
        else:
            p[0] = p[1]

    def p_var_decl_expr(self, p):
        """ var_decl_expr : var_decl_type ID
                        | var_decl_expr COMMA ID
        """
        if len(p) == 3:
            typename = p[1]
            vn = VarNode(typename, p[2], self._token_coord(p, 2))
            p[0] = [vn]
        else:   # list
            vn0 = p[1][0]
            vn = VarNode(vn0.type, p[3], self._token_coord(p, 3))
            p[1].append(vn)
            p[0] = p[1]

    def p_var_decl_type(self, p):
        """ var_decl_type : STRUCT ID
                        | INVIS_STRUCT ID
                        | UNION ID
        """
        p[0] = '{} {}'.format(p[1], p[2])

    def p_callee_expression_decl(self, p):
        """ callee_expression_decl : callee_expression SEMI
        """
        p[0] = [p[1]]

    def p_callee_expression(self, p):
        """ callee_expression : ID LPAREN RPAREN
        """
        callee = p[1]
        cn = CallNode(callee, self._token_coord(p, 1))
        p[0] = cn

    def p_binary_expression_decl(self, p):
        """ binary_expression_decl   : binary_expression1 SEMI
                                    | binary_expression2 SEMI
                                    | binary_expression3 SEMI
                                    | binary_expression4 SEMI
        """
        p[0] = p[1]

    def p_binary_expression_1(self, p):
        """ binary_expression1   : cast_expression LINKOP INT_CONST_DEC LINKOP cast_expression
                        | cast_expression LINKOP INT_CONST_HEX LINKOP cast_expression
        """
        left, linkop, right = p[1], p[2], p[5]
        base = 16 if '0x' in p[3] else 10
        jump = int(p[3], base)
        ln = LinkNode(left, right, linkop, jump, coord=self._token_coord(p, 2))
        p[0] = [ln]
        _mlog.debug(f'{left} {linkop} {right}')

    def p_binary_expression_2(self, p):
        """ binary_expression2   : cast_expression LINKOP cast_expression
                        | binary_expression2 LINKOP cast_expression
        """
        if len(p) >= 4:
            if type(p[1]) is list:
                ln0 = p[1][-1]     # last link
                left = ln0.right
                right = p[3]
                linkop = p[2]
                ln = LinkNode(left, right, linkop, coord=self._token_coord(p, 2))
                p[1].append(ln)
                p[0] = p[1]
                _mlog.debug(f'{left} {linkop} {right}')
            else:
                left, linkop, right = p[1], p[2], p[3]
                ln = LinkNode(left, right, linkop, coord=self._token_coord(p, 2))
                p[0] = [ln, ]
                _mlog.debug(f'{left} {linkop} {right}')
        else:
            p[0] = p[1]

    def p_binary_expression_3(self, p):
        """ binary_expression3   : cast_expression LINKOP INT_CONST_DEC
                        | cast_expression LINKOP INT_CONST_HEX
        """
        left, linkop, right = p[1], p[2], ''
        base = 16 if '0x' in p[3] else 10
        jump = int(p[3], base)
        ln = LinkNode(left, right, linkop, jump, coord=self._token_coord(p, 2))
        p[0] = [ln]
        _mlog.debug(f'{left} {linkop} {right}')

    def p_binary_expression_4(self, p):
        """ binary_expression4   : INT_CONST_DEC LINKOP cast_expression
                        | INT_CONST_HEX LINKOP cast_expression
        """
        left, linkop, right = '', p[2], p[3]
        base = 16 if '0x' in p[1] else 10
        jump = int(p[1], base)
        ln = LinkNode(left, right, linkop, jump, coord=self._token_coord(p, 2))
        p[0] = [ln]
        _mlog.debug(f'{left} {linkop} {right}')

    def p_cast_expression_1(self, p):
        """ cast_expression : ID
                            | cast_expression PERIOD ID
                            | cast_expression ARROW ID
        """
        if len(p) == 2:
            p[0] = p[1]
        else:
            node = "{}{}{}".format(p[1], p[2], p[3])
            p[0] = node.replace('->', '.')

    def p_empty(self, p):
        'empty : '
        p[0] = None

    def p_error(self, p):
        # If error recovery is added here in the future, make sure
        # _get_yacc_lookahead_token still works!
        #
        if p:
            self._parse_error(
                'before: %s' % p.value,
                self._coord(lineno=p.lineno,
                            column=self.clex.find_tok_column(p)))
        else:
            self._parse_error('At end of input', self.clex.filename)

import os.path
if not os.path.exists(os.path.join(_fpath, 'func_yacctab.py')):
    curr = os.getcwd()
    os.chdir(_fpath)
    prepare_yacctab(GVCFuncParser())
    os.chdir(curr)
